#pragma comment(lib , "Wininet.lib")
#include <WinInet.h>
#include <map>
#include <locale>
#include "../Text/Convert.h"
#include "../Stream/Stream.h"

#include "NetCode.h"

enum PROTOCOL_TYPE
{
	PROTOCOL_HTTP,
	PROTOCOL_HTTPS,
	PROTOCOL_UNKNOW
};

bool UpperEqual( ctchar& lhs, ctchar& rhs)
{
	return std::toupper( lhs , std::locale() ) == std::toupper(rhs , std::locale());
}

bool UpperCompateString( cstring& lhs , cstring& rhs )
{
	return std::equal( lhs.begin(),lhs.end() , rhs.begin() , UpperEqual);
}

int32 GetProtocolType( cstring& protocol , PROTOCOL_TYPE& type )
{
	    type = PROTOCOL_UNKNOW;
	if(     UpperCompateString( protocol , _RBT("http" ) )  )
		type = PROTOCOL_HTTP;
	else if( UpperCompateString(protocol , _RBT("https") )  )
		type = PROTOCOL_HTTPS;

	if( type == PROTOCOL_UNKNOW )
		return E_UNKNOW_PROTOCOL;
	return R_SUCCEED;
}

enum PARSE_SETP
{
	SETP_PROTOCOL,
	SETP_SPLIT_PROTOCOL,
	SETP_SPLIT,
	SETP_SERVER,
	SETP_PORT,
	SETP_RESOURCE,
	SETP_END
};

class Url
{
	string        m_server;
	string        m_userName;
	string        m_password;
	uint32        m_port;
	PROTOCOL_TYPE m_protocol;
	string        m_resource;

	int32 SetDefaultPort()
	{
		if(m_protocol == PROTOCOL_HTTPS)
			m_port = 443;
		else if( m_protocol == PROTOCOL_HTTP )
			m_port = 80;
		else
			return E_UNKNOW_PROTOCOL;
		return R_SUCCEED;
	}
public:

	cstring& GetServer() const
	{
		return m_server;
	}
	
	cstring& GetResource() const
	{
		return m_resource;
	}

	cstring& GetUserName() const
	{
		return m_userName;
	}

	cstring& GetPassword() const
	{
		return m_password;
	}

	PROTOCOL_TYPE
			GetProtocol() const
	{
		return m_protocol;
	}

	uint32 GetPort() const
	{
		return m_port;
	}


	int32 Initialize( const string& url )
	{
		typedef string::const_iterator iterator;

		ctchar* first = url.c_str();
		ctchar* last  = url.c_str() + url.size() + 1;
		ctchar* token = url.c_str();
		int32   code  = R_SUCCEED;

		int32    setp  = SETP_PROTOCOL;

#define NEXT_SETP() token = first + 1; setp++;continue
#define BREAK_END() if( *first == _RBT('\0')  ) { setp = SETP_END; break; }

		for(;first!=last;++first)
		{
			switch( setp )
			{
			case SETP_PROTOCOL:
				{
					if( *first == _RBT(':') )
					{
						code  = GetProtocolType( string( token , first ) , m_protocol );
						NEXT_SETP();
					}
					continue;
				}
			case SETP_SPLIT:
			case SETP_SPLIT_PROTOCOL:
				{
					if( *first != _RBT('/') )
						return E_URL_FORMAT;
					NEXT_SETP();
				}
			case SETP_SERVER:
				{
					if( *first == _RBT('/') || 
						*first == _RBT(':') ||
						*first == _RBT('\0') )
					{
						m_server = string( token , first );

						if(*first == _RBT('/') || 
						   *first == _RBT('\0' ) )
						{
							setp = SETP_PORT;
							SetDefaultPort();
						}
						BREAK_END();
						NEXT_SETP();
					}
					continue;
				}
			case SETP_PORT:
				{
					if( *first == _RBT('\0') || 
						*first == _RBT('/')    )
					{
						code = Convert::ToUInt32( string( token , first ) , m_port );
						if(code != R_SUCCEED)
							return code;
						BREAK_END();
						NEXT_SETP();
					}
					continue;
				}
			case SETP_RESOURCE:
				{
					if( *first == _RBT('\0') )
					{
						m_resource = string( token , first );
						NEXT_SETP();
					}
					continue;
				}
			default:
				break;
			}
		}

		if( setp != SETP_END )
			return E_URL_FORMAT;
		return R_SUCCEED;

#undef NEXT_SETP()
#undef BREAK_END()
	}
};

static cstring HTTP_METHOD_GET  = _RBT("GET");
static cstring HTTP_METHOD_POST = _RBT("POST");

class HttpConnect
{
	HINTERNET m_internet;
	HINTERNET m_http;

protected:
	int32 Initalize()
	{
		m_internet = ::InternetOpen( 0 , INTERNET_OPEN_TYPE_PRECONFIG ,
								 NULL , NULL , 0 );
		if( m_internet == 0 )
			return E_INTERNET_OPEN;
		return R_SUCCEED;
	}

	int32 Uninitalize()
	{
		int32 codeInternet   = R_SUCCEED;
		int32 codeConnection = R_SUCCEED;

		if( m_internet == 0)
			return R_SUCCEED;

		if( ::InternetCloseHandle(m_http) )
			codeConnection = E_CLOSE_HTTPCONNECTION;

		if( !::InternetCloseHandle(m_internet) )
			codeInternet = E_CLOSE_HTTPINTERNET;

		if( codeInternet != R_SUCCEED && codeConnection != R_SUCCEED )
			return E_CLOSE_HTTP;

		if(codeInternet != R_SUCCEED)
			return codeInternet;

		if(codeConnection != R_SUCCEED)
			return codeConnection;

		return R_SUCCEED;
	}

	int32 Connection( const Url& url )
	{
		m_http = ::InternetConnect( 
									m_internet ,
									url.GetServer().c_str()   ,
									url.GetPort()             ,
									url.GetUserName().c_str() ,
									url.GetPassword().c_str() ,
									INTERNET_SERVICE_HTTP     ,
									0 , 0 
									);

		if(m_http == 0)
			return E_CONNECTION;
		return R_SUCCEED;
	}

public:

	HINTERNET GetConnection()
	{
		return m_http;
	}

};






class HttpStream :
	public Stream
{
public:
	enum StreamMethod
	{
		STREAM_READ,
		STREAM_WRITE,
		STREAM_READWRITE
	};
protected:
	StreamMethod m_method;
	HINTERNET    m_http;
	uint64       m_size;
	uint64       m_position;

	bool CheckInvalid()
	{
		if( m_http == NULL )
			return true;
		return false;
	}

#define CHECK_INVALID() if( CheckInvalid() ) { return E_HTTPSTREAM_INVALID; }
public:

	int32 Initalize(HINTERNET http , cuint64 size , StreamMethod method )
	{
		m_http     = http;
		m_size     = size;
		m_method   = method;
		m_position = 0;
		return R_SUCCEED;
	}

	virtual int32 CanRead()
	{
		CHECK_INVALID();

		if( m_method == STREAM_READ || m_method == STREAM_READWRITE )
			return R_SUCCEED;
		return E_HTTPSTREAM_CANREAD;
	}

	virtual int32 CanWrite()
	{
		CHECK_INVALID();

		if( m_method == STREAM_WRITE || m_method == STREAM_READWRITE )
			return R_SUCCEED;
		return E_HTTPSTREAM_CANWRITE;
	}
	virtual int32 CanSeek()
	{
		CHECK_INVALID();
		return E_HTTPSTREAM_SEEK;
	}

	virtual int32 GetSize(     uint64& size     )
	{
		CHECK_INVALID();
		size = m_size;
		return R_SUCCEED;
	}

	virtual int32 GetPosition( uint64& position )
	{
		CHECK_INVALID();
		position = m_position;
		return R_SUCCEED;
	}

	virtual int32 Seek(        uint64  position , SEEK_POSITION flag = CURRENT )
	{
		return E_HTTPSTREAM_SEEK;
	}

	virtual int32 Read(  byte* block , uint32 blockSize , uint32& fileRead )
	{

		CHECK_INVALID();

		int32 code = CanRead();

		if(code != R_SUCCEED)
			return code;

		DWORD readSize = 0;
		BOOL  netRead  = FALSE;
		netRead = InternetReadFile( m_http , (LPVOID)block , blockSize , &readSize );
		if( netRead == FALSE )
			return E_HTTPSTREAM_READ;
		fileRead = readSize;
		m_position += readSize;

		return R_SUCCEED;
	}

	virtual int32 Write( byte* block , uint32 blockSize , uint32& fileWrite )
	{

		CHECK_INVALID();

		int32 code = CanWrite();

		if(code != R_SUCCEED)
			return code;

		DWORD writeSize = 0;
		BOOL  netWrite  = FALSE;
		netWrite = InternetWriteFile( m_http , (LPVOID)block , blockSize , &writeSize );

		if( netWrite == FALSE )
			return E_HTTPSTREAM_WRITE;

		fileWrite   = writeSize;
		m_position += writeSize;

		return R_SUCCEED;
	}

	virtual int32 Flush()
	{
		return R_SUCCEED;
	}

	virtual int32 EndOfStream()
	{
		if( m_position >= m_size )
			return R_SUCCEED;
		return R_NOT_ENDOFSTREAM;
	}

	virtual int32 Close()
	{
		m_http     = NULL;
		m_size     = 0;
		m_position = 0;
		return R_SUCCEED;
	}
};




class HttpRequest :
	public HttpConnect
{
protected:

	typedef std::map< string , string > heads;

	string      m_method;
	string      m_version;
	HINTERNET   m_request;
	heads       m_heads;
	string      m_agent;
	bool        m_keepAlive;
	bool        m_cache;
	string      m_accept;


	int32 AppendHead( string& head , cstring& key , cstring& value )
	{

		if(value.empty())
			return E_EMPTYHEADVALUE;

		head.append( key          );
		head.append( _RBT(": ")   );
		head.append( value        );
		head.append( _RBT("\r\n") );
	}

	int32 GetHeadString( string& head )
	{

		head.clear();

		typedef heads::iterator iterator;

		iterator first = m_heads.begin();
		iterator last  = m_heads.end();

		AppendHead( head , _RBT("Accept"    ) , m_accept);
		AppendHead( head , _RBT("User-Agent") , m_agent );
		if(m_keepAlive)
			AppendHead(head , _RBT("Connection"), _RBT("Keep-Alive"));
		if(!m_cache)
			AppendHead(head , _RBT("Pragma"    ), _RBT("no-cache")  );

		for( ; first != last ; first++ )
		{
			AppendHead( head , first->first , first->second );
		}

		return R_SUCCEED;
	}
public:

	int32 Initialize()
	{
		m_method    = HTTP_METHOD_GET;
		m_version   = _RBT("HTTP/1.1");
		m_agent     = _RBT("RBT Http Request Client");
		m_keepAlive = true;
		m_cache     = false;
		m_accept    = _RBT("*/*");


		int32 code = R_SUCCEED;

		code = HttpConnect::Initalize();

		if(code != R_SUCCEED)
			return code;
	}

	int32 Uninitalize()
	{
		int32 code = R_SUCCEED;
		if( m_request != 0 )
		{
			if( !::InternetCloseHandle( m_request ) )
			{
				code = E_CLOSE_REQUEST;
			}
		}

		int32 codeConnection = HttpConnect::Uninitalize();

		if(codeConnection != R_SUCCEED)
			return codeConnection;

		return code;
	}


	int32 OpenRequest( const Url& url )
	{
		int32 code = R_SUCCEED;

		if( ! (url.GetProtocol() == PROTOCOL_HTTP || url.GetProtocol() == PROTOCOL_HTTPS ))
		{
			return E_PROTOCOL_TYPE;
		}

		code = Initalize();

		if( code != R_SUCCEED )
			return code;

		code = Connection( url );

		if( code != R_SUCCEED )
			return code;



		DWORD flag = 0;

		if(m_keepAlive)
			flag |= INTERNET_FLAG_KEEP_CONNECTION;
		if(!m_cache)
			flag |= INTERNET_FLAG_NO_CACHE_WRITE;

		m_request = ::HttpOpenRequest( 
			GetConnection()           ,
			m_method.c_str()          ,
			url.GetResource().c_str() ,
			m_version.c_str()         ,
			0                         ,
			0                         ,
			INTERNET_FLAG_DONT_CACHE  ,
			0 );

		if(m_request == 0)
			return E_OPENREQUEST;

		return R_SUCCEED;

	}

	int32 GetResponseStream( HttpStream& httpStream )
	{
		BOOL httpQuery      = FALSE;
		DWORD contentLength = 0;
		DWORD contentSize   = sizeof(DWORD);
		httpQuery = ::HttpQueryInfo( m_request , HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER , &contentLength , &contentSize , NULL  );

		if( httpQuery == FALSE )
			return E_QUERY_CONTENT_LENGTH;

		return httpStream.Initalize( m_request , contentLength , HttpStream::STREAM_READ );
	}

	int32 Navigate( string url )
	{
		int32 code   = R_SUCCEED;
		Url   httpUrl;

		code = httpUrl.Initialize(url);
		if(code != R_SUCCEED)
			return code;

		code = OpenRequest( httpUrl );

		if( code != R_SUCCEED )
			return code;

		string head;
		GetHeadString( head );

		BOOL send = ::HttpSendRequest( 
			m_request   ,
			head.c_str(),
			head.size() ,
			0           ,
			0
			);

		if( !send )
		{
			return E_SENDREQUEST;
		}

		return R_SUCCEED;
	}
};



class HttpArgument
{

	struct ArgumentItem
	{
		int32  type;
		string value;

		ArgumentItem()
		{

		}

		ArgumentItem( const ArgumentItem& argument ) : type( argument.type ) , value( argument.value )
		{

		}

		ArgumentItem( int32 tp , cstring& v ) : type(tp) , value(v)
		{

		}
	};

	typedef std::map< string , ArgumentItem > Arguments;
protected:
	Arguments m_argument;
public:

	HttpArgument()
	{
		SetDefaultType();
	}

	enum ArgumentType
	{
		ARG_ENCODING_UTF8    = 1,
		ARG_ENCODING_UNICODE = 2,
		ARG_ENCODING_ANSI    = 3,

		ARG_MASK_BYTE        = 1<<8,
		ARG_MASK_TEXT        = 2<<8,

		ARG_TYPE_DIGIT       = 1<<16,
		ARG_TYPE_TEXT        = 2<<16
	};

protected:
	int32 m_default;
public:
	void TrimString( string& source )
	{
		string::iterator first = source.begin();
		string::iterator last  = source.end();

		for(;first!=last;++first)
		{
			if( std::isspace(*first,std::locale()) )
				continue;
			else
				break;
		}

		string::reverse_iterator rfirst = source.rbegin();
		string::reverse_iterator rlast  = string::reverse_iterator( first );

		for(;rfirst!=rlast;++rfirst)
		{
			if( std::isspace(*rfirst,std::locale()) )
				continue;
			else
				break;
		}

		source.assign( first , rfirst.base() );
	}

	ArgumentType GetEncoding( cint32& type )
	{
		return (ArgumentType)( type & 0xFF );
	}

	ArgumentType GetMask( cint32& type )
	{
		return (ArgumentType)( type & 0xFF00 );
	}

	ArgumentType GetType( cint32& type )
	{
		return (ArgumentType)( type & 0xFF0000 );
	}

	void SetDefaultType( int32 argumentType = ARG_ENCODING_UTF8 | ARG_MASK_TEXT )
	{
		m_default = argumentType;
	}

	typedef Arguments::iterator iterator;

	iterator begin()
	{
		return m_argument.begin();
	}

	iterator end()
	{
		return m_argument.end();
	}

	int32 Set( cstring& key , cstring& value , int32 type = 0 )
	{
		string saveKey = key;

		TrimString(saveKey);

		if(saveKey.empty())
			return E_ARGUMENT_KEY;

		if( GetType( type ) != 0 && GetType(type) != ARG_TYPE_TEXT )
			return E_ARGUMENT_TYPE;

		type |= ARG_TYPE_TEXT;

		m_argument[key] = ArgumentItem( type , value );

		return R_SUCCEED;
	}

	int32 Set( cstring& key , ctchar* value , int32 type = 0 )
	{
		return Set(key,string(value),type);
	}

	template< class T >
	int32 Set( cstring& key , const T& value , int32 type = 0 )
	{

		string saveKey = key;

		TrimString(saveKey);

		if(saveKey.empty())
			return E_ARGUMENT_KEY;

		if( GetType(type) != 0 && GetType(type) != ARG_TYPE_DIGIT )
			return E_ARGUMENT_TYPE;

		type |= ARG_TYPE_DIGIT;

		string text;
		int32 code = R_SUCCEED;
		code = Convert::ToString( value , text );
		if(code != R_SUCCEED)
			return code;


		m_argument[saveKey] = ArgumentItem( type , text );

		return R_SUCCEED;
	}

	int32 GetString( string& text )
	{
		iterator first = begin();
		iterator last  = end();

		int32 code = R_SUCCEED;

		for(;first != last; ++first)
		{
			string key   = first->first;
			string value = first->second.value;
			int32  type  = first->second.type;

			ArgumentType tpEncoding = GetEncoding(type);
			ArgumentType tpMask     = GetMask(type);
			ArgumentType tpType     = GetType(type);

			if( tpEncoding == 0 )
				tpEncoding = GetEncoding(m_default);
			if( tpMask == 0 )
				tpMask = GetMask(m_default);

			if( tpType == ARG_TYPE_TEXT )
			{
				if( tpEncoding == ARG_ENCODING_UTF8 )
				{
					
				}
			}
			else if(tpType == ARG_TYPE_DIGIT)
			{
				text.append( key );
				text.append(_RBT("="));
				text.append( value );
			}
			else
			{
				return E_ARGUMENT_INVALID_TYPE;
			}
		}
	}
};
